// Copyright (c) 2019 Presto Labs Pte. Ltd.
// Author: xguo

#pragma once

#include <functional>
#include <memory>
#include <set>
#include <string>
#include <unordered_map>
#include <utility>
#include <vector>

#include "coin2/exchange/base/market/enums.h"
#include "coin2/exchange/base/order/utils.h"

namespace coin2::exchange::base::order {

class SymbologyCache {
 public:
  using GetProductHolderFunc =
      std::function<const IProductHolder*(const IProduct& product, int64_t timestamp)>;

 public:
  SymbologyCache() = default;
  SymbologyCache(SymbologyCache&&) = default;
  SymbologyCache(const SymbologyCache&) = delete;
  ~SymbologyCache() = default;

  // Add products and currencies, in initialization stage.
  void AddProduct(std::unique_ptr<IProduct>&& product);
  void AddProduct(std::unique_ptr<IProduct>&& product, int64_t timestamp);
  void AddCurrency(std::unique_ptr<ICurrency>&& currency);
  void SetGetProductHolder(GetProductHolderFunc func);

  // Accessor
  const IProduct* GetProductByNative(const std::string& symbol) const {
    return product_native_map_.at(symbol);
  }
  const IProduct* GetProductByNorm(const std::string& symbol) const {
    return product_absolute_map_.at(symbol);
  }
  const ICurrency* GetCurrencyByNative(const std::string& symbol) const {
    return currency_native_map_.at(symbol);
  }
  const ICurrency* GetCurrencyByNorm(const std::string& symbol) const {
    return currency_norm_map_.at(symbol);
  }
  const IProductInfo* GetProductInfoByNative(const std::string& symbol) const {
    return product_info_native_map_.at(symbol);
  }
  const IProductInfo* GetProductInfoByNorm(const std::string& symbol) const {
    return product_info_absolute_map_.at(symbol);
  }
  const base::symbology::IProductHolder* GetProductHolder(const IProduct& product, int64_t ts)
      const {
    return get_product_holder_(product, ts);
  }

  const auto& GetProducts() const { return product_set_; }
  const auto& GetCurrencies() const { return currency_set_; }
  const auto& GetProductNative() const { return product_native_; }

  // Sometimes useful.
  const auto& GetCurrenciesNativeUpper() const { return currency_native_upper_; }
  const auto& GetCurrenciesNativeLower() const { return currency_native_lower_; }

  bool has_product_absolute_norm(const std::string& name) const {
    return product_absolute_map_.count(name) > 0;
  }
  bool has_product_native(const std::string& name) const {
    return product_native_map_.count(name) > 0;
  }
  bool has_currency_native(const std::string& name) const {
    return currency_native_map_.count(name) > 0;
  }
  bool has_currency_norm(const std::string& name) const {
    return currency_norm_map_.count(name) > 0;
  }

  bool Contains(const ICurrency& currency) const;
  bool Contains(const IProduct& product) const;

  void set_mea(const MarketExchangeApi& mea) {
    mea_ = mea;
    is_exwallet_ = false;
    is_spot_ = false;
    is_margin_ = false;
    is_futures_ = false;
    if (mea_.market == market::MarketType::ExWallet) {
      is_exwallet_ = true;
    } else if (mea_.market == market::MarketType::Spot) {
      is_spot_ = true;
    } else if (mea_.market == market::MarketType::Margin) {
      is_margin_ = true;
    } else if (mea_.market == market::MarketType::Futures) {
      is_futures_ = true;
    } else if (mea_.market == market::MarketType::Options) {
      is_options_ = true;
    }
    is_swap_ = false;
    if (mea.api.find("swap") != std::string::npos) {
      is_swap_ = true;
    }
    is_linear_ = false;
    if (mea.api.find("linear") != std::string::npos) {
      is_linear_ = true;
    }
  }
  const auto& mea() const { return mea_; }
  bool is_exwallet() const { return is_exwallet_; }
  bool is_spot() const { return is_spot_; }
  bool is_margin() const { return is_margin_; }
  bool is_futures() const { return is_futures_; }
  bool is_options() const { return is_options_; }
  bool is_swap() const { return is_swap_; }
  bool is_linear() const { return is_linear_; }

  void SetPortfolioMarginTrading(bool portfolio_margin_trading) {
    portfolio_margin_trading_ = portfolio_margin_trading;
  }
  bool IsPortfolioMarginTrading() const { return portfolio_margin_trading_; }
  bool IsDeprecatedExchange(const IProduct& product, const MarketExchangeApi& mea) const {
    if (product.exchange() == ExchangeType::Prex) {
      return true;
    } else if (product.exchange() == ExchangeType::Huobi) {
      return true;
    } else if (product.exchange() == ExchangeType::Binance) {
      return true;
    } else if (product.exchange() == ExchangeType::Bybit) {
      return true;
    } else if (product.exchange() == ExchangeType::Bitmex) {
      return true;
    } else if (
        product.exchange() == ExchangeType::Okex && (mea.api == "v5" || mea.api == "v5-swap")) {
      return true;
    } else if (product.exchange() == ExchangeType::Ftx) {
      return true;
    } else if (product.exchange() == ExchangeType::Deribit) {
      return true;
    } else if (product.exchange() == ExchangeType::Kucoin) {
      return true;
    } else if (
        product.exchange() == ExchangeType::Phemex && (mea.api == "v1-linear" || mea.api == "v1")) {
      return true;
    }
    return false;
  }

 private:
  void add_extra_symbol(const ExchangeType& exchange_type);

 private:
  MarketExchangeApi mea_;
  bool is_exwallet_;
  bool is_spot_;
  bool is_margin_;
  bool is_futures_;
  bool is_options_;
  bool is_swap_;
  bool is_linear_;
  // Storage
  std::vector<std::unique_ptr<IProduct>> product_storage_;
  std::vector<std::unique_ptr<ICurrency>> currency_storage_;

  // Index for searching
  std::set<const IProduct*> product_set_;
  std::set<const ICurrency*> currency_set_;

  std::set<std::string> product_absolute_norm_;
  std::set<std::string> product_native_;
  std::set<std::string> currency_norm_;
  std::set<std::string> currency_native_;
  std::set<std::string> currency_native_upper_;
  std::set<std::string> currency_native_lower_;

  std::unordered_map<std::string, const IProduct*> product_native_map_;
  std::unordered_map<std::string, const IProduct*> product_absolute_map_;

  std::unordered_map<std::string, const IProductInfo*> product_info_native_map_;
  std::unordered_map<std::string, const IProductInfo*> product_info_absolute_map_;

  std::unordered_map<std::string, const ICurrency*> currency_norm_map_;
  std::unordered_map<std::string, const ICurrency*> currency_native_map_;

  GetProductHolderFunc get_product_holder_;

  bool is_extra_symbol_initialized = false;

  bool portfolio_margin_trading_ = false;  // for binance only
};

}  // namespace coin2::exchange::base::order
